Tricks of the Mac Game Programming Gurus is the ultimate resource for beginning to expert game programmers who already have general programming experience.
The book contains instruction, tips, and source code from the top names in Mac game development today. The secrets, examples, and code can’t be found anywhere else! These are the tried-and-true tricks that work behind the scenes in the most popular commercial and shareware Mac games.
Throughout the book, you’ll find special interviews with some of the most well-known Mac game programmers. They reveal their secret solutions created while they developed their popular games. The CD-ROM includes various helpful development tools, libraries, utilities as well as all the source code from the book, Metrowerks’ CodeWarrior Lite environment, and Many game demos, plus shareware and commercial games.
You can purchase Tricks of the Mac Game Programming Gurus (over 900 pages plus CD-ROM) directly through Inside Mac Games for $50.00 plus shipping. To order, check out the IMG Bookstore folder found on this CD-ROM.
Here’s an excerpt from Tricks of the Mac Game Programming Gurus!
Chapter 9: QuickDraw 3D
QuickDraw 3D is Apple’s latest addition to the QuickDraw family of graphics routines. This new suite of Toolbox calls provides even a novice game programmer with the ability to quickly and easily create highly detailed, three-dimensional worlds for Power Macs with only a minimal understanding of 3-D mathematics. Apple has provided a library of powerful 3-D routines numbering in the hundreds, and the Inside Macintosh: QuickDraw 3D volume is one of the largest volumes in the Inside Macintosh series. But do not let this new library’s friendly interface fool you, for QuickDraw 3D is very powerful, fairly fast, and produces very high-quality rendered scenes.
QuickDraw 3D can render in only two different modes: wireframe and Z-Buffer. However, it does not currently support “depth sort” rendering, which is perhaps the most common and fastest rendering method used in 3-D video games. You may have heard the depth sort render also referred to as the “painter’s algorithm.” To render an image with this method, all of the polygons in a scene are sorted from farthest away to nearest. Then, they are drawn in that order: back to front so that closer polygons are drawn over farther polygons (see figure 9.1). There are two benefits to a depth sort renderer. First, it’s very fast (more time is sometimes spent doing the 3-D math to rotate, scale, and translate polygons than is spent actually drawing them). Second, it uses far less memory than the Z-Buffer method. Depth sort rendering is used in almost all polygon-based games and is the method of choice in most dedicated game machines such as the 3DO, Sega Saturn, and Sony PlayStation.
 
Figure 9.1 Depth sorting.
Even though the depth sort method is the most commonly used method in video games, QuickDraw 3D does not currently use it. The next best thing that it provides is the Z-Buffer renderer. Z-Buffer rendering is much slower than depth sort rendering, and Z-Buffer rendering requires a lot of RAM. The basic principle behind this rendering algorithm is that for each pixel on the screen, there is a value which represents the distance from the camera to the currently drawn pixel. This distance value is called the Z value, hence the “Z-Buffer.” This way, when a new polygon is being drawn, each pixel’s Z value is compared with the Z value of the pixel already there. If the new pixel has a smaller Z value (i.e. it’s closer to the camera) than the old pixel, then the new pixel is drawn over the old one.
The reward for the increased complexity and decreased performance of the Z-Buffer algorithm is a much higher-quality rendering, because this algorithm allows for polygons to cleanly intersect each other (something which is almost impossible to do with a depth sort). Figure 9.2 shows the classic example of an impossible situation for a depth sort renderer. In this figure, every polygon overlaps another polygon; therefore, no polygon is in front or in back. Since a depth sort renderer relies entirely on accurate back-to-front sorting of polygons, it will fail in this example. A Z-Buffer renderer, however, does not care about the depth sorting of polygons, thus it can render this case without any problem.
 
Figure 9.2 The classic “impossible case” scenario for depth sort rendering.
The QuickDraw 3D designers have done an excellent job in optimizing their Z-Buffer renderer, so much so that obtaining the frame rates necessary in certain kinds of video games is easily within reach. You probably won’t be doing any full-screen flight simulators with QuickDraw 3D, but a lot of possibilities still exist. With QuickDraw 3D and a good knowledge of fighting games, you could easily create a 3-D polygon fighting game such as Sega’s Virtua Fighter. Puzzle games that don’t require high frame rates could be done incredibly well. Even adventure games could be written to make excellent use of QuickDraw 3D to generate camera fly-throughs of the enemy village, or perhaps your character’s eye view of the inside of a dragon’s cave.
It should also be pointed out here that QuickDraw 3D is not just a software library. There is also an accelerator card for the PCI-based Power Macintoshes which can boosts performance up to 12 times depending on the implementation of QuickDraw 3D in your game! Even though you would be limiting your game’s market, you could write a game specifically for use with the accelerator card, in which case you probably could do a full-screen flight simulator. As a matter of fact, you could probably do the best-looking flight simulator the Mac has ever known!
Aside from QuickDraw 3D’s quick rendering speed and quality, it touts many other great features:
• Built-in Geometries —Support for boxes, cones, cylinders, disks, ellipses, ellipsoids, general polygons, lines, polylines, meshes, NURB Curves, NURB Patches, points, tori, triangles, and trigrids. However, not all of these are supported in QuickDraw 3D 1.0.
• Hierarchical Attributes —Attributes may be assigned to groups of objects, individual objects, faces of objects, and vertices of faces. Attributes include color, ambient reflection values, specular colors, and so on.
• Multiple Drawing Styles —The ability to adjust the quality of the rendering. This includes techniques such as polygon backface removal, vertex color interpolation, and tessellation of spline-based geometries.
• Powerful Lighting Features —The four types of lights include ambient lights, directional lights, point lights, and spot lights. Spot lights can easily be aimed and focused. Light attenuation (“fall-off” values) can be individually set for each light.
• Object Picking —The ability to provide easy user interface with the mouse. Clicked objects and their associated data can easily be found from mouse and window information.
• 3D Math Utilities —A large set of 3-D mathematical utilities for performing simple tasks such as setting vectors and points, as well as the more involved tasks such as calculating distances, dot products, and matrix manipulations.
Getting Started
The most difficult part of QuickDraw 3D is getting your first application up and running. Unlike plain old QuickDraw, which requires nothing more than a SetPort() and a LineTo() call to see results, QuickDraw 3D requires a lot of setup code. Even to display a line on the screen, you must perform many initialization tasks including setting the camera, creating lights, initializing the draw environment, and so on. But before we get started, it’s important to have a basic understanding of the QuickDraw 3D Object Hierarchy and a rough idea how attributes work.
Objects
Almost every element in QuickDraw 3D is an “object.” Cameras are objects, geometries are objects, even attributes, shaders, and styles are objects. All of these objects are linked together hierarchically (see figure 9.3). The highest-level object is the group object. A group object (or simply a “group”) is simply an object that contains references to more objects, even other group objects. It is a convenient way to collect all of the objects in a scene or part of a scene into one easily manageable entity. If you have 50 missiles flying around your scene, it is much easier to attach all of them to one group called gMyMissleGroupObject and draw that single object than it would be to always draw the 50 missiles one at a time. It’s also more efficient to pass groups of objects to QuickDraw 3D than to pass lots of individual objects one at a time.
 
Figure 9.3 The generic QuickDraw 3D hierarchy.
Attributes
The lowest-level object in the QuickDraw 3D hierarchy is the attribute object. An attribute is an object that defines how a geometric object should be drawn in a scene. Attributes include such things as what color the object is, how shiny the object is, and how to apply a texture map to the object. Attributes are always assigned to geometric objects or elements of a geometric object. These elements are usually polygon faces and vertices.
Attributes function by a system known as “inheritance.” Inheritance means that attributes of higher-level elements are passed down to lower-level elements of a geometry unless those lower-level elements have overriding attributes of their own. For example, the color attribute of a vertex will override the color attribute of a face, which in turn will override the color attribute of the geometry. If you assign a mesh object a color attribute of green, the geometry will appear blue if the individual faces of the mesh were assigned a color attribute of blue.
QuickDraw 3D is immense[md]this entire book could be dedicated to a discussion of the intricacies of QuickDraw 3D’s hierarchy and attribute rules, but since that would bore us all to tears, detailed discussions about attributes, objects, and hierarchies will be left up to the massive Inside Macintosh: QuickDraw 3D volume. Instead, we’re going to delve right into the code to get us up and running.
Initializing QuickDraw 3D
The first thing we must do to get up and running is to initialize QuickDraw 3D. In your application’s startup routine, you must insert the following code to activate QuickDraw 3D.
/* Initialize QD 3D */
TQ3Status myStatus;
myStatus = Q3Initialize();
if ( myStatus == kQ3Failure )
DoFatalAlert("\pQ3Initialize returned failure!");
QuickDraw 3D Toolbox routines often return an error status of either kQ3Failure or kQ3Success. Unlike many other Macintosh tool calls, the Boolean values here are reversed. kQ3Success equals 1, and kQ3Failure equals 0; therefore, be careful not to write code like you may have in the past, in which you perform simple error checks like this:
OSErr error;
error = ToolboxCall();
if (error)
HandleQ3ror();
Since the returned value for an error is no longer 1, this will give you the opposite effect that you want. It is good practice in QuickDraw 3D to always check error conditions by explicitly comparing against kQ3Success or kQ3Failure.
Creating a Draw Environment
Once we have initialized QuickDraw 3D, we need to create a draw environment before we can do anything. The draw environment defines all of the basic information that QuickDraw 3D needs to know in order to set up a scene. We do this by executing the following ten steps.
1. Create a window in which the scene will be drawn.
2. Create a new view object.
3. Attach a draw context to the view.
4. Attach a renderer to the view.
5. Attach a camera to the view.
6. Attach a light group to the view.
7. Create an interpolation style object.
8. Create a backfacing style object.
9. Create a fill style object.
10. Create a shader object.
At first glance, this may seem like a lot to do[md]and it is. Luckily for you, however, I have provided all of the code necessary to perform the above ten tasks. The code necessary to set up our drawing environment teaches many of QuickDraw 3D’s most commonly used principles, so it is a good idea to thoroughly read the following examples and to get a good feel for how and why things work. If it makes sense to you, then you should have no problem with anything else in the QuickDraw 3D world. The first step is trivial: If you’re ready to tackle QuickDraw 3D, I presume you are familiar with creating Mac windows. The technique is listed in the code and I won’t repeat it here.
Creating the View Object
The following routines initialize a view object and perform steps 2 through 6 as described above.
/******************* CREATE MY VIEW *************************/
//
// This creates our view object.
//
// INPUT : none
// OUTPUT : none
//
// NOTE: gMyViewObject is a global variable of type TQ3ViewObject
The routine CreateMyView() above initializes a QuickDraw 3D view object in a very straightforward manner. It creates the new view object simply by calling the Q3View_New() function, and then it proceeds to attach the required objects to the view. The first object attached is the draw context object (see CreateMyDrawContex() coming up). A draw context defines where a scene will be rendered, such as GWorld, screen, and so on.
The next object to be assigned to our view is a renderer object. The routine Q3Renderer_NewFromType() takes a predefined renderer type as input. The setting kQ3RendererTypeInteractive tells QuickDraw 3D to use the Z-Buffer renderer and to automatically use hardware acceleration if available.
Note
There is an additional call that can be made immediately after creating a renderer which can improve performance with hardware accelerators.
This line of code tells QuickDraw 3D not to double buffer if the acceleration hardware is capable of displaying fast enough without it. This can theoretically improve performance up to 10 percent under the right conditions. Even on fast renderers, however, you still may see some flickering due to the elimination of the double buffer.
Now that we set up where our scene will appear, we proceed to define our scene’s camera via CreateMyCamera(). As you will see later, this routine creates a camera object that defines where our “eye” is located in 3-D space and what it is looking at.
The final object which needs to be attached to our view is the light group object. The light group object is a group object that contains all of the lights in our scene. Any scene must have at least one light if you want to be able to see anything. A scene rendered with no lights will be black.
Creating the Draw Context
Let’s take a closer look at some of the routines that were called in the code above. The first routine that we called was CreateMyDrawContex(). Here is the code for CreateMyDrawContex():
/**************** CREATE MY DRAW CONTEXT *********************/
//
// Creates a DRAW CONTEXT which will be attached to the fly-through VIEW object.
// The draw context defines how a frame is drawn to a window or off-screen buffer.
//
// INPUT : pointer to the window we will be drawing in
This routine creates and returns a draw context object. To create the object, we first set up a TQ3DrawContextData structure and a TQ3MacDrawContextData structure. They will contain all of the information necessary to define our draw context.
The first two items initialized are the clearImageMethod field and clearImageColor field. The clearImageColor field defines the RGB color for our scene’s background. In this example, we have set the clear color to dark blue. RGB colors in QuickDraw 3D are expressed as three floating point numbers between 0.0 and 1.0, where 0.0 is the darkest value and 1.0 is the brightest value for each color component red, green, and blue. Therefore, an RGB value of 1.0, 1.0, 1.0 is white, and the value 0.0, 0.0, 0.0 is black. Solid red would be 1.0, 0.0, 0.0. However, it is important to note that there are two types of color definitions: TQ3ColorRGB and TQ3ColorARGB. The TQ3ColorARGB structure is used in the code sample above. The A stands for “Alpha.” The alpha value of a TQ3ColorARGB structure determines the amount of transparency for special hardware effects. We won’t be worrying about alpha values in our discussion, so we’ll just set it to 1.0. The value 1.0 tells QuickDraw 3D that there is no transparency. A value of 0.0 would be maximum transparency.
The pane, which is the next field to be initialized, is analogous to the QuickDraw Rect structure because it defines the four edges of a rectangle. QuickDraw 3D uses the pane to determine where in the window we want it to render our scene. In the code above, we are setting the pane to the size of the entire window by getting the window’s portRect structure and copying it to the draw context’s pane structure. Suppose we did not want to draw into the entire window—suppose we wanted to leave a 20-pixel-wide area on the right side of the window for a game’s status bar. To do this, simply change the line:
That will reduce the right side of the pane to leave a 20-pixel-wide gap.
There is also a paneState flag which is set. If this flag is set to kQ3False, then the pane min/max settings will be ignored and the entire window will be used by default. Since in our example we are using the whole window, we could have simplified this code by just setting the paneState flag to kQ3False and not even bothered with the pane bounds.
The next item in the draw context’s data structure is the doubleBufferState flag. Double buffering is the process of drawing to one draw buffer while displaying another. We have set the doubleBufferState flag to kQ3True and you will usually want to have it set that way when you are rendering directly to a window. Turning double buffering off will result in a tremendous amount of tearing on the screen as you animate the scene. Power Macs equipped with hardware acceleration for QuickDraw 3D may be able to live without double buffering at the cost of minimal screen tearing.
Our draw context data structure now needs to be attached to a TQ3MacDrawContextData structure. The TQ3MacDrawContextData structure defines Macintosh-specific information about the draw context. Because QuickDraw 3D will be available for Microsoft Windows as well as the Macintosh, there are separate data structures that define the draw context for each machine. Luckily, we don’t care about Windows, so we’ll pretend that QuickDraw 3D exists only for the Macintosh (the way it should be, eh?). Should you want to render to an off-screen buffer and not a window, then you need to use a TQ3PixmapDrawContextData structure instead. You cannot render to an off-screen buffer such as a GWorld by using a TQ3MacDrawContextData structure.
The TQ3MacDrawContextData structure’s window parameter is set to the WindowPtr that we passed into CreateMyDrawContex(). This will tell QuickDraw 3D which window to render into. Make sure that a window exists before doing this. Passing a bogus window will obviously have, oh, shall we say, “adverse” side effects.
The next parameter set is the library parameter. For our purposes, this should always be set to kQ3Mac2DLibrary_None. Viola! All the data we need to define a draw context has been set. Once we call Q3MacDrawContext_New(), we have our new draw context object which gets passed back to the calling routine.
The only part of this routine that you will probably ever change is the clearImageColor setting, because you will want to experiment with background colors for your scene. All the other settings will most likely remain unchanged.